package gggoal

import (
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/gkindp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/secure/rng"
	"wzgames/internal/services/games"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

type GGoal struct {
	games.GGame

	zn   string
	cfg  *gkindp.GoalConfig
	gift *Gift
}

func NewGoal(inst *gamep.GameInst) (games.IGameService, error) {
	goal := &GGoal{
		gift: &Gift{},
	}
	goal.zn = "goal"
	goal.SetGameInst(inst)
	err := goal.updateConf()
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return goal, err
	}
	goal.gift.Init(goal.cfg)

	return goal, nil
}

func init() {
	games.RegisterGame(106, NewGoal)
	games.RegisterMarker(106, NewGMarker)
}

func (a *GGoal) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &gkindp.GoalConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[Goal] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

func (a *GGoal) Update() {
	err := a.updateConf()
	if err == nil {
		a.gift.Update(a.cfg)
	}
}

func (a *GGoal) Destroy() {

}

func (a *GGoal) Login(tb *games.GameTable) {
	seed, nextSeed := rng.BeginSeed(tb.Player.AgtID(), tb.Player.ID())
	tb.Marker.InitServerSeed(seed, nextSeed)

	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	a.clientInit(tb)
}

func (a *GGoal) Logout(tb *games.GameTable) {
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ != nil && betR != nil {
		if betQ.GameAct == mcomm.GameAction_GameBet && !betR.CashOut {
			betQ.GameAct = mcomm.GameAction_GameCancel
			a.doSubmit(tb, betQ, betR)
		} else if betQ.GameAct == mcomm.GameAction_GameUpStep && betQ.ImRet != nil && betQ.ImRet.Win != mcomm.GameResult_GameLose {
			betR.CashOut = true
			betQ.GameAct = mcomm.GameAction_GameCashOut
			betQ.ImRet.GameAct = betQ.GameAct
			betQ.ImRet.Dateline = dateutil.UnixMilli()
			a.doSubmit(tb, betQ, betR)
		}
	}
}

func (a *GGoal) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(6))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(3))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyAvatar, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteSfsArrayPre(constant.KeyFreeBets, uint16(0))

	xw.Write(a.cfg.GetConfigBin())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleArrayS(constant.KeyFastBets, tb.GameCurr.FloatValueLimit.FastBets)
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyDefaultBetValue, tb.GameCurr.FloatValueLimit.DefaultBetValue)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		xw.WriteUtfStringArray(constant.KeyFastBets, tb.GameCurr.StrValueLimit.FastBets)
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyDefaultBetValue, tb.GameCurr.StrValueLimit.DefaultBetValue)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GGoal) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerBet:
			a.betHandler(tb, m)
		case constant.HandlerAutoPlay:
			a.autoPlayHandler(tb, m)
		case constant.HandlerCashOut:
			a.cashOutHandler(tb, m)
		case constant.HandlerOpenCell:
			a.openCellHandler(tb, m)
		case constant.HandlerServerSeed:
			a.ServerSeedHandler(tb)
		case constant.HandlerGetHistory:
			a.getBetHistory(tb, m)
		case constant.HandlerRoundDetails:
			a.getRoundDetails(tb, m)
		default:
			logger.Warn("[Goal] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	default:
		logger.Warn("[Goal] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GGoal) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeed, tb.Marker.NextServerSeed256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GGoal) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.InPlay() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseBet)
		return
	}
	tb.Marker.SetPlay(true)
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betType := data.GetUtfString(constant.KeyType)
	if betType != constant.KeySm && betType != constant.KeyMd && betType != constant.KeyLg {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}

	betObj := tb.GenBetObj(mcomm.GameAction_GameBet)

	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseBet, true)
			return
		}
	}
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	tb.UpBetObj(betObj)
	result := a.gift.Calculate(tb.Marker.ServerSeed(), clientSeed, betType)
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = false
	goalBet := &statement.GoalBetInfo{
		ServerSeed: tb.Marker.ServerSeed(),
		ClientSeed: clientSeed,
		BetType:    betType,
		Result:     result,
	}
	nextSeed := rng.NextSeed(0, 0, 0)
	tb.Marker.RefreshSeed(nextSeed)
	tb.Marker.(*GMarker).SetBet(betObj, goalBet)
	code := a.doSubmit(tb, betObj, goalBet)
	if code != 0 {
		a.RetErrWithBala(tb, code, constant.ResponseBet, true)
		return
	}
	a.betResponse(tb, betObj, goalBet)
}

func (a *GGoal) betResponse(tb *games.GameTable, betObj *statement.PlayBet, goalBet *statement.GoalBetInfo) {
	_bet, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(6))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, false)
	xw.WriteDoubleS(constant.KeyWinAmount, _bet)
	xw.WriteDoubleS(constant.KeyCashout, _bet)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GGoal) calcProfit(tb *games.GameTable, digs, precision int32, betType, betAmt string) (string, float64, string, float64) {
	multiF := a.gift.CalcMultiplier(a.cfg, int(digs), betType)
	multiplier := decimal.NewFromFloat(multiF)
	d1, _ := decimal.NewFromString(betAmt)
	ret := d1.Mul(multiplier).Truncate(precision)
	winRet, rFloat := tb.CheckMaxWin(ret.String())
	return multiplier.String(), multiF, winRet, rFloat
}

func (a *GGoal) cashOutHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.(*GMarker).IsOpening() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseCashOut)
		return
	}
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ == nil || betR == nil || betQ.GameAct == mcomm.GameAction_GameCancel || betR.CashOut || betQ.ImRet == nil ||
		betQ.ImRet.Win == mcomm.GameResult_GameLose || betR.Bomb {
		a.RetErr(tb, response.ErrForbidden, constant.ResponseCashOut)
		return
	}
	betR.CashOut = true
	preAct := betQ.GameAct
	betQ.GameAct = mcomm.GameAction_GameCashOut
	multiStr, multiplier, winStr, winAmount := a.calcProfit(tb, int32(len(betR.Digs)), tb.Seat.Precision, betR.BetType, betQ.BetAmt)
	betR.Multiplier = multiplier
	betQ.ImRet.GameAct = betQ.GameAct
	betQ.ImRet.Payout = multiStr
	betQ.ImRet.WinAmt = winStr
	betQ.ImRet.Dateline = dateutil.UnixMilli()
	code := a.doSubmit(tb, betQ, betR)
	if code != 0 {
		a.RetErr(tb, code, constant.ResponseCashOut)
		betR.CashOut = false
		betQ.GameAct = preAct
		return
	}
	tb.Marker.SetPlay(false)

	_bet, _ := stringutil.ToFloat64(betQ.BetAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseCashOut)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(8))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBetAmount, _bet)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmount)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteDoubleS(constant.KeyCoefficientSum, betR.Multiplier)
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, true)
	xw.WriteIntArrayS(constant.KeyMines, betR.Result)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GGoal) doSubmit(tb *games.GameTable, betObj *statement.PlayBet, goalBet *statement.GoalBetInfo) int16 {
	switch betObj.GameAct {
	case mcomm.GameAction_GameImResult:
		betObj.Detail = a.goalBetInfo(goalBet)
		return tb.ImResultBet(betObj)
	case mcomm.GameAction_GameBet:
		betObj.Detail = a.goalBetInfo(goalBet)
		return tb.Bet(betObj)
	case mcomm.GameAction_GameCashOut, mcomm.GameAction_GameLoseSettle:
		betObj.ImRet.Detail = a.goalBetInfo(goalBet)
		return tb.Payout(betObj)
	case mcomm.GameAction_GameCancel:
		return tb.BetCancel(betObj)
	case mcomm.GameAction_GameUpStep:
		betObj.ImRet.Detail = a.goalBetInfo(goalBet)
		return tb.GameUpStep(betObj)
	}

	return response.ErrInternal
}

func (a *GGoal) goalBetInfo(goalBet *statement.GoalBetInfo) string {
	b, err := json.Marshal(goalBet)
	if err != nil {
		logger.Error("[Goal] do submit fail on json.Marshal goal bet", zap.Error(err))
		return ""
	}
	return byteutil.SliceByteToString(b)
}

func (a *GGoal) openCellHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	if tb.Marker.(*GMarker).CheckInOpen() {
		a.RetErr(tb, response.ErrFrequence, constant.ResponseOpenCell)
		return
	}
	defer tb.Marker.(*GMarker).ClearOpen()
	betQ := tb.Marker.(*GMarker).GetBetQ()
	betR := tb.Marker.(*GMarker).GetBetR()
	if betQ == nil || betR == nil || (betQ.GameAct != mcomm.GameAction_GameBet && betQ.GameAct != mcomm.GameAction_GameUpStep) ||
		betR.CashOut || betR.Bomb {
		a.RetErr(tb, response.ErrForbidden, constant.ResponseOpenCell)
		return
	}
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCell)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCell)
		return
	}
	row, _ := stringutil.ToInt32(data.GetUtfString("row"), 0)
	col, _ := stringutil.ToInt32(data.GetUtfString("column"), 0)
	goalRC := a.gift.GetGoalRowCol(a.cfg, betR.BetType)
	if row < 0 || (row > goalRC.Row-1) || col < 0 || col > (goalRC.Col-1) {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCell)
		return
	}
	digsLen := int32(len(betR.Digs))
	if digsLen >= col+1 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCell)
		return
	}
	if digsLen == 0 && col > 0 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseOpenCell)
		return
	}
	bomb := false
	if betR.Result[col] == row {
		bomb = true
		betR.Bomb = true
	}
	betQ.GameAct = mcomm.GameAction_GameUpStep
	betQ.Dateline = dateutil.UnixMilli()
	tb.PlayInputData(betQ, exMsg)
	if betQ.ImRet == nil {
		betQ.ImRet = &statement.PlayPayout{
			BetId:   betQ.BetId,
			GameAct: betQ.GameAct,
			GameId:  betQ.GameId,
		}
	}

	a.openCellResponse(tb, row, col, bomb, betQ, betR, goalRC)
}

func (a *GGoal) openCellResponse(tb *games.GameTable, row, col int32, bomb bool, betQ *statement.PlayBet, betR *statement.GoalBetInfo, goalRC *gkindp.GoalRowCol) {
	var winAmt, cashout, nextTile float64
	allDug := false
	betR.Digs = append(betR.Digs, &gkindp.GoalDig{
		Row:     row,
		Col:     col,
		MaxRows: goalRC.Row,
		Bomb:    bomb,
	})
	oldMultiplier := betR.Multiplier
	betQ.ImRet.Dateline = dateutil.UnixMilli()
	betQ.RoundId += 1
	betQ.ImRet.RoundId = betQ.RoundId
	if bomb {
		betQ.ImRet.Win = mcomm.GameResult_GameLose
		winAmt = 0
		cashout = 0
		nextTile = 0
		betR.Multiplier = 0
		betQ.ImRet.Payout = ""
		betQ.ImRet.WinAmt = "0"
	} else {
		cdigLen := int32(len(betR.Digs))
		multiStr, multiplier, winStr, winF := a.calcProfit(tb, cdigLen, tb.Seat.Precision, betR.BetType, betQ.BetAmt)
		if goalRC.Col == cdigLen {
			allDug = true
			betR.CashOut = true
			betR.AllDug = true
		} else {
			cdigLen += 1
		}
		betQ.ImRet.Payout = multiStr
		betQ.ImRet.WinAmt = winStr
		betR.Multiplier = multiplier
		winAmt = winF
		cashout = winAmt
		//_, _, _, nextTile = a.calcProfit(tb, cdigLen, tb.Seat.Precision, betR.BetType, betQ.BetAmt)
		nextTile = a.gift.CalcMultiplier(a.cfg, int(cdigLen), betR.BetType)
		betQ.ImRet.Win = mcomm.GameResult_GameWin
	}

	preAct := betQ.GameAct
	if allDug {
		betQ.GameAct = mcomm.GameAction_GameCashOut
		betQ.ImRet.GameAct = betQ.GameAct
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			betR.Digs = deleteLastDig(betR.Digs)
			betR.CashOut = false
			betR.AllDug = false
			betQ.ImRet.Payout = ""
			betQ.ImRet.WinAmt = "0"
			betQ.RoundId -= 1
			betQ.GameAct = preAct
			betQ.ImRet.GameAct = betQ.GameAct
			return
		}
		tb.Marker.SetPlay(false)
	} else if betQ.ImRet.Win == mcomm.GameResult_GameLose {
		betQ.GameAct = mcomm.GameAction_GameLoseSettle
		betQ.ImRet.GameAct = betQ.GameAct
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			return
		}
		tb.Marker.SetPlay(false)
	} else {
		betQ.GameAct = mcomm.GameAction_GameUpStep
		code := a.doSubmit(tb, betQ, betR)
		if code != 0 {
			a.RetErr(tb, code, constant.ResponseOpenCell)
			betR.Digs = deleteLastDig(betR.Digs)
			betR.Multiplier = oldMultiplier
			betQ.RoundId -= 1
			betQ.GameAct = preAct
			return
		}
	}

	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseOpenCell)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(7))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betQ.ImRet.Win))
	xw.WriteDoubleS(constant.KeyNextTile, nextTile)
	xw.WriteDoubleS(constant.KeyCashout, cashout)
	xw.WriteSfsObjectPre(constant.KeyOpenCell, uint16(3))
	xw.WriteIntS(constant.KeyColumn, col)
	xw.WriteIntS(constant.KeyRow, row)
	xw.WriteIntS(constant.KeyMaxRows, goalRC.Row)

	xw.WriteSfsObjectPre(constant.KeyMine, uint16(3))
	xw.WriteIntS(constant.KeyColumn, col)
	xw.WriteIntS(constant.KeyRow, betR.Result[col])
	xw.WriteIntS(constant.KeyMaxRows, goalRC.Row)

	if bomb || allDug {
		_bet, _ := stringutil.ToFloat64(betQ.BetAmt, 0)
		xw.WriteSfsObjectPre(constant.KeyEndGame, uint16(8))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteDoubleS(constant.KeyBetAmount, _bet)
		xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
		xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
		xw.WriteDoubleS(constant.KeyCoefficientSum, betR.Multiplier)
		xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
		xw.WriteBoolS(constant.KeyWin, response.IfWin(betQ.ImRet.Win))
		xw.WriteIntArrayS(constant.KeyMines, betR.Result)
	} else {
		xw.WriteIntS(constant.KeyNextColumn, col+1)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func deleteLastDig(a []*gkindp.GoalDig) []*gkindp.GoalDig {
	newOne := a[:0]
	l := len(a)
	for i := 0; i < l-1; i++ {
		newOne = append(newOne, a[i])
	}

	return newOne
}

func (a *GGoal) autoPlayHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseAutoPlay)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseAutoPlay)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseAutoPlay)
		return
	}
	betType := data.GetUtfString(constant.KeyType)
	if betType != constant.KeySm && betType != constant.KeyMd && betType != constant.KeyLg {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseAutoPlay)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseAutoPlay)
		return
	}

	goalRC := a.gift.GetGoalRowCol(a.cfg, betType)
	selCells := data.GetInt32Array(constant.KeySelectedCells)
	lc := len(selCells)
	var selectCells []*gkindp.GoalDig
	validCells := true
	for i := 0; i < lc; i++ {
		selC := selCells[i]
		if selC <= goalRC.Row-1 && selC >= 0 {
			selectCells = append(selectCells, &gkindp.GoalDig{
				Row:     selC,
				Col:     int32(i),
				MaxRows: goalRC.Row,
				Bomb:    false,
			})
		} else {
			validCells = false
			break
		}
	}
	if lc < 1 || lc > int(goalRC.Col) || !validCells {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseAutoPlay)
		return
	}

	betObj := tb.GenBetObj(mcomm.GameAction_GameImResult)

	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseAutoPlay, true)
			return
		}
	}
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseAutoPlay)
		return
	}
	tb.UpBetObj(betObj)
	result := a.gift.Calculate(tb.Marker.ServerSeed(), clientSeed, betType)
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = false
	goalBet := &statement.GoalBetInfo{
		ServerSeed: tb.Marker.ServerSeed(),
		ClientSeed: clientSeed,
		BetType:    betType,
		Digs:       selectCells,
		Result:     result,
	}
	a.calcAutoResult(tb, betObj, goalBet)
	nextSeed := rng.NextSeed(0, 0, 0)
	tb.Marker.RefreshSeed(nextSeed)
	code := a.doSubmit(tb, betObj, goalBet)
	if code != 0 {
		a.RetErrWithBala(tb, code, constant.ResponseAutoPlay, true)
		return
	}
	a.autoBetResponse(tb, betObj, goalBet)
}

func (a *GGoal) calcAutoResult(tb *games.GameTable, betObj *statement.PlayBet, goalBet *statement.GoalBetInfo) {
	win := true
	var winAmt = "0"
	lc := len(goalBet.Digs)
	betObj.RoundId = int64(lc)
	for i := 0; i < lc; i++ {
		if goalBet.Result[i] == goalBet.Digs[i].Row { // bomb
			goalBet.Digs[i].Bomb = true
			win = false
			break
		}
	}
	imResult := &statement.PlayPayout{
		BetId:   betObj.BetId,
		GameAct: betObj.GameAct,
		Win:     mcomm.GameResult_GameLose,
		GameId:  betObj.GameId,
	}

	if win {
		multiStr, multi, winStr, _ := a.calcProfit(tb, int32(lc), tb.Seat.Precision, goalBet.BetType, betObj.BetAmt)
		goalBet.Multiplier = multi
		goalBet.CashOut = true
		imResult.Payout = multiStr
		winAmt = winStr
		imResult.Win = mcomm.GameResult_GameWin
	}
	imResult.WinAmt = winAmt
	imResult.Dateline = dateutil.UnixMilli()
	betObj.ImRet = imResult
}

func (a *GGoal) autoBetResponse(tb *games.GameTable, betObj *statement.PlayBet, goalBet *statement.GoalBetInfo) {
	betAmt, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(betObj.ImRet.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseAutoPlay)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)

	xw.WriteSfsObjectPre(constant.KeyEndGame, uint16(8))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteDoubleS(constant.KeyCoefficientSum, goalBet.Multiplier)
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betObj.ImRet.Win))
	xw.WriteIntArrayS(constant.KeyMines, goalBet.Result)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GGoal) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}

func (a *GGoal) getRoundDetails(tb *games.GameTable, m *sfs.TSfsObject) {
	data := m.GetContent()
	if data == nil {
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		return
	}
	betId, _ := stringutil.ToInt64(data.GetUtfString(constant.KeyRoundId), 0)
	if betId <= 0 {
		return
	}

	ret, code := tb.GetUserBetDetail(betId)
	if code != 0 {
		return
	}

	goalBet := &statement.GoalBetInfo{}
	err := json.Unmarshal(byteutil.StringToSliceByte(ret.Detail), goalBet)
	if err != nil {
		logger.Error("[Goal] bet fail on json.Unmarshal goal bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return
	}

	betAmt, _ := stringutil.ToFloat64(ret.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(ret.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseRoundDetails)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(12))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteLongS(constant.KeyBetId, betId)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteLongS(constant.KeyTimeRound, ret.BetTime)
	xw.WriteDoubleS(constant.KeyPayout, calcutil.RoundFloor(goalBet.Multiplier))
	xw.WriteStringS(constant.KeyClientSeed, goalBet.ClientSeed)
	xw.WriteStringS(constant.KeyCurrency, ret.Curr)
	xw.WriteBoolS(constant.KeyIsFreeBetGame, ret.FreeBet)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(ret.Win))
	xw.WriteStringS(constant.KeyServerSeed, goalBet.ServerSeed)

	xw.WriteSfsObjectPre(constant.KeyDetails, uint16(2))
	ldigs := len(goalBet.Digs)
	xw.WriteSfsArrayPre(constant.KeySelectedCells, uint16(ldigs))
	for _, dig := range goalBet.Digs {
		xw.WriteUByte(sfs.SfsObject)
		xw.WriteUShort(uint16(3))
		xw.WriteIntS(constant.KeyColumn, dig.GetCol())
		xw.WriteIntS(constant.KeyRow, dig.GetRow())
		xw.WriteIntS(constant.KeyMaxRows, dig.GetMaxRows())
	}
	lmines := len(goalBet.Result)
	xw.WriteSfsArrayPre(constant.KeyMineCells, uint16(lmines))
	for i, mine := range goalBet.Result {
		xw.WriteUByte(sfs.SfsObject)
		xw.WriteUShort(uint16(2))
		xw.WriteIntS(constant.KeyColumn, int32(i))
		xw.WriteIntS(constant.KeyRow, mine)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}
